Skip to content

feat(kilo-pass): guard welcome promo by payment fingerprint#3526

Merged
RSO merged 13 commits into
mainfrom
mark/kilo-pass-card-promo-guard
May 28, 2026
Merged

feat(kilo-pass): guard welcome promo by payment fingerprint#3526
RSO merged 13 commits into
mainfrom
mark/kilo-pass-card-promo-guard

Conversation

@markijbema
Copy link
Copy Markdown
Contributor

@markijbema markijbema commented May 27, 2026

Summary

  • Prevent repeated Kilo Pass introductory 50% promo claims across accounts by permanently claiming the first positively paid monthly settlement for supported reusable Stripe payment fingerprints (card, sepa_debit, us_bank_account, bacs_debit, and au_becs_debit).
  • Persist settlement-derived eligibility reasons and apply an affirmative Stripe promo decision: reused fingerprints, zero-value initial settlements, and unresolved positive settlements cannot receive the introductory promo, while ordinary monthly-ramp behavior remains available.
  • Show a customer-safe post-payment warning strictly scoped to the completed Checkout session, and retain only minimal payment-fingerprint anti-abuse evidence through account deletion.
  • Define reused-fingerprint Kilo Pass referral disqualification in the governing spec for the not-yet-live Kilo Pass referral processor.

Verification

  • No manual browser verification performed in this CLI session; the warning reuses the existing post-Checkout awarding surface and semantic alert primitive.

Visual Changes

N/A - adds an existing-style warning alert on the post-payment awarding screen when the introductory offer is unavailable.

Reviewer Notes

  • Annual subscriptions remain unaffected by the reusable-payment-fingerprint rule.
  • A confirmed settled payment method without a supported reusable fingerprint may still use existing account-history eligibility; unresolved positive Stripe settlements fail closed for the introductory promo.
  • Invoice settlement supports both PaymentIntent and direct Charge payment representations.
  • The final branch diff contains one generated Drizzle migration for this feature; earlier branch-local intermediate migrations were dropped and regenerated via drizzle-kit.
  • Kilo Pass referral conversion/reward processing is schema/spec-prepared but not currently implemented in application logic; the persisted reason is available for that future integration.

@kilo-code-bot
Copy link
Copy Markdown
Contributor

kilo-code-bot Bot commented May 27, 2026

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

The incremental commits expand fingerprint-claim coverage from card-only to five reusable Stripe payment instrument types (card, sepa_debit, us_bank_account, bacs_debit, au_becs_debit), add direct Charge payment resolution alongside PaymentIntent, gate fingerprint claiming and promo eligibility on positive settlement amount, and introduce a SettlementUnresolved retry-aware idempotency path. All schema, migration, application logic, and test changes are consistent and correct.

Incremental Changes (since e653f2c)
  • packages/db/src/schema-types.tsKiloPassWelcomePromoPaymentFingerprintType enum added with 5 instrument types; KiloPassWelcomePromoEligibilityReason extended with NoSupportedFingerprint, NoPositiveSettlement, SettlementUnresolved; no issues
  • packages/db/src/schema.ts — table renamed from kilo_pass_welcome_promo_card_claims to kilo_pass_welcome_promo_payment_fingerprint_claims; composite PK (stripe_payment_method_type, stripe_fingerprint) replaces single-column PK; check constraint on type enum added; no issues
  • packages/db/src/migrations/0145_safe_preak.sql — replaces old card-only migration; composite PK, type check constraint, updated reason enum check; no locking risk (new table); no issues
  • packages/db/src/migrations/meta/_journal.json — squashed back to single migration 0145; correct per AGENTS.md squash guidance; no issues
  • apps/web/src/lib/kilo-pass/stripe-handlers-utils.tsSettledInvoicePaymentMethod discriminant updated from card/non_card to reusable/without_supported_fingerprint/unknown; getReusablePaymentMethodResult and getReusableChargeResult cover all 5 instrument types; getExpandedCharge follows same optional-chaining guard as getExpandedPaymentMethod; loop now handles charge-type payments before payment_intent; no issues
  • apps/web/src/lib/kilo-pass/stripe-handlers-invoice-paid.tsclaimMonthlyPaymentFingerprint extracted as separate function called only on positive monthly settlements; getOrCreateInitialMonthlyWelcomePromoReason updated to skip SettlementUnresolved for retry; reason=null throw is a sound safety net (path is unreachable under current call convention); update no longer uses isNull guard — safe because SettlementUnresolved skip logic ensures idempotency; no issues
  • apps/web/src/lib/kilo-pass/usage-triggered-bonus.tsrequiresSettledPaymentDecision flag gates first-month promo on explicit resolved Stripe reasons; isAllowedStripeWelcomePromoReason correctly allows FirstPaymentFingerprintClaim, MissingFingerprint, NoSupportedFingerprint; no issues
  • apps/web/src/routers/kilo-pass-router.tsgetCheckoutReturnState sessionId made required (was optional), client updated with enabled guard; welcomePromoIneligibleDueToReusedCard renamed to welcomePromoIneligibleDueToReusedFingerprint; no issues
  • apps/web/src/app/payments/kilo-pass/awarding/KiloPassAwardingCreditsClient.tsx — empty-string fallback with enabled: false guard prevents invalid requests when session_id absent; no issues
  • Test files — new tests cover direct Charge settlement, zero-value invoice blocking promo without claiming, SettlementUnresolved retry, all 5 instrument type positive/negative paths; no issues
  • .specs/impact-referrals.md and .plans/ — updated to reflect broadened instrument scope; no issues
All Files Reviewed (23 files total)
  • .plans/kilo-pass-welcome-promo-card-fingerprint-guard.md
  • .specs/impact-referrals.md
  • apps/web/src/app/payments/kilo-pass/awarding/KiloPassAwardingCreditsClient.tsx
  • apps/web/src/lib/kilo-pass/enums.ts
  • apps/web/src/lib/kilo-pass/stripe-handlers-invoice-paid.ts
  • apps/web/src/lib/kilo-pass/stripe-handlers-invoice-paid.test.ts
  • apps/web/src/lib/kilo-pass/stripe-handlers-utils.ts
  • apps/web/src/lib/kilo-pass/usage-triggered-bonus.ts
  • apps/web/src/lib/kilo-pass/usage-triggered-bonus.test.ts
  • apps/web/src/lib/kilo-pass/usage-triggered-bonus.unit.test.ts
  • apps/web/src/lib/user/index.ts
  • apps/web/src/lib/user/index.test.ts
  • apps/web/src/routers/kilo-pass-router.ts
  • apps/web/src/routers/kilo-pass-router.test.ts
  • packages/db/src/schema.ts
  • packages/db/src/schema-types.ts
  • packages/db/src/schema.test.ts
  • packages/db/src/migrations/0145_safe_preak.sql
  • packages/db/src/migrations/0145_even_sugar_man.sql (deleted)
  • packages/db/src/migrations/0146_small_albert_cleary.sql (deleted)
  • packages/db/src/migrations/meta/0145_snapshot.json
  • packages/db/src/migrations/meta/0146_snapshot.json (deleted)
  • packages/db/src/migrations/meta/_journal.json

Reviewed by claude-4.6-sonnet-20260217 · 2,063,892 tokens

Review guidance: REVIEW.md from base branch main

Copy link
Copy Markdown
Contributor

@RSO RSO left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code looks good, please make sure to test this functionality before merging.

Comment on lines +130 to +134
? params.stripe.paymentIntents?.retrieve
? await params.stripe.paymentIntents.retrieve(rawPaymentIntent, {
expand: ['payment_method'],
})
: null
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In what case would stripe.paymentIntents.retrieve not exist? Probably just in the tests? The code looks ugly

@markijbema markijbema changed the title feat(kilo-pass): guard welcome promo by payment card feat(kilo-pass): guard welcome promo by payment fingerprint May 28, 2026
@RSO RSO merged commit a445458 into main May 28, 2026
52 checks passed
@RSO RSO deleted the mark/kilo-pass-card-promo-guard branch May 28, 2026 12:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants